#include "declarations.h"
extern int lineNumber;


values_struct* findKey(symbol_table& table, string key)
{
    for (symbol_table::iterator it = table.begin(); it < table.end(); it++) 
	{
		map<string, values_struct>::iterator find_it = it->find(key);   
		if (find_it != it->end()) return &(find_it->second);
    }
    
	return NULL;
}

bool keyExistsInCurrScope(symbol_table& table, string key)
{
	map<string, values_struct> curr_scope = table.front();
	bool found = curr_scope.count(key) > 0;
	return found;	
}

void addSymbol(symbol_table& table, string key, values_struct value)
{
	table.front()[key] = value;
}

void pushScope(symbol_table& table)
{
	table.push_front(map<string, values_struct>());
}

void popScope(symbol_table& table)
{
	table.pop_front();
}

char* getTypeName(type_name type)
{
	char *name = new char[20]; 
	if(type == bool_type)
		strcpy(name, "bool");
	else if(type == int_type)
		strcpy(name, "int");
	else if(type == double_type)
		strcpy(name, "double");
	else if(type == string_type)
		strcpy(name, "string");
	else if(type == void_type)
		strcpy(name, "void");
	return name;
}

void validateMainFunDecl(function_table& functions)
{
	int count = functions.count("main");
	if(count <= 0)
	{
		fprintf(stderr, "ERROR -> \'main\' function does not exist\n");
		exit(26);
	}
}

void validateVariableDeclaration(values_struct& typ, values_struct& id, values_struct& rule, symbol_table& table)
{
	if(!keyExistsInCurrScope(table, string(id.name))) 
	{
		rule.type = typ.type;		
		id.type = typ.type;
		
		addSymbol(table, string(id.name), id);
	}
	else
	{
		rule.type = error_type;
		fprintf(stderr, "ERROR(line %i)->Variable already exists: %s\n", lineNumber, id.name);
		exit(1);
	}
}
void validateLeftValue(values_struct& a, values_struct& rule, symbol_table& table)
{
	
		values_struct *values = findKey(table, a.name);
		if(values != NULL)
		{
			rule.type = values->type;
			
		}
		else
		{
			rule.type = error_type;
			fprintf(stderr, "ERROR(line %i)->Using non-declared variable: %s\n", lineNumber, a.name);
			exit(2);
		}	
}
void validateLeftValueArray(values_struct& a, values_struct& rule, symbol_table& table)
{
	
	values_struct *values = findKey(table, a.name);
	if(values != NULL)
	{
		if(values->is_array){
			rule.type = values->type;
		}else{
			fprintf(stderr, "ERROR(line %i)-> Variable is not an array, it can\'t use brackets : %s\n", lineNumber, a.name);
			exit(3);
		}
	}
	else
	{
		rule.type = error_type;
		fprintf(stderr, "ERROR(line %i)->Using non-declared variable: %s\n", lineNumber, a.name);
		exit(4);
	}	
}

void validateAssignmentOp(values_struct& a, values_struct& b, values_struct& rule)
{
	if(a.type == b.type)
	{
		rule.type = a.type;
	}
	else
	{
		rule.type = error_type;
		fprintf(stderr, "ERROR(line %i)->Incongruent types in assignment: %s %s\n", lineNumber, getTypeName(a.type), getTypeName(b.type));
		exit(5);
	}
}

void validateArithOp(values_struct& a, values_struct& b, values_struct& rule)
{
	bool intCond = (a.type == int_type) && (b.type == int_type);
	bool doubleCond = (a.type == double_type) && (b.type == double_type);

	if(intCond || doubleCond) 
	{
		rule.type = a.type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent types in arithmetic expression: %s %s\n", lineNumber, getTypeName(a.type), getTypeName(b.type));
		rule.type = error_type;
		exit(6);
	}
}

void validateModuleOp(values_struct& a, values_struct& b, values_struct& rule)
{
	bool intCond = (a.type == int_type) && (b.type == int_type);
	if(intCond)
	{
		rule.type = a.type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent types in module operation: %s %s\n", lineNumber, getTypeName(a.type), getTypeName(b.type));
		rule.type = error_type;
		exit(7);
	}
}

void validateMinusOp(values_struct& a, values_struct& rule)
{
	bool intCond = (a.type == int_type);
	bool doubleCond = (a.type == double_type);
	if(intCond || doubleCond)
	{
		rule.type = a.type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent type in minus expression: %s\n", lineNumber, getTypeName(a.type));
		rule.type = error_type;
		exit(8);
	}
}

void validateRelationEqualOp(values_struct& a, values_struct& b, values_struct& rule)
{
	bool intCond = (a.type == int_type) && (b.type == int_type);
	bool doubleCond = (a.type == double_type) && (b.type == double_type);
	if(intCond || doubleCond)
	{
		rule.type = bool_type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent types in compare expression: %s %s\n", lineNumber, getTypeName(a.type), getTypeName(b.type));
		rule.type = error_type;
		exit(9);
	}
}

void validateBoolOp(values_struct& a, values_struct& b, values_struct& rule)
{
	if(a.type == bool_type && b.type == bool_type)
	{
		rule.type = bool_type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent types in bool operation: %s %s\n", lineNumber, getTypeName(a.type), getTypeName(b.type));
		rule.type = error_type;
		exit(10);
	}
}

void validateBoolOp(values_struct& a, values_struct& rule)
{
	if(a.type == bool_type)
	{
		rule.type = bool_type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent type in bool operation: %s\n", lineNumber, getTypeName(a.type));
		rule.type = error_type;
		exit(11);
	}
}

void validatePrintArg(values_struct& a, values_struct& rule)
{
	if(a.type == int_type || a.type == string_type || a.type == bool_type)
	{
		rule.type = void_type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent type in print arguments: %s\n", lineNumber, getTypeName(a.type));
		rule.type = error_type;
		exit(12);
	}
}

void validateBoolCond(values_struct& condition)
{
	if(condition.type != bool_type)
	{
		fprintf(stderr, "ERROR(line %i)->Incongruent type in \'if\' argument: %s\n", lineNumber, getTypeName(condition.type));
		exit(13);
	}
}

void addFunction(map<string, function_struct>& functions, string name, type_name type)
{
	functions[name].type = type;
}

bool functionExists(symbol_table& table, string name)
{
	if(findKey(table, name) == NULL)
	{
		return false;
	}

	return true;
}

bool functionExists(function_table& functions, string name)
{
	if(functions.count(name) > 0)
	{
		return true;
	}

	return false;
}

void validateFunctionDeclaration(symbol_table& table, map<string, function_struct>& functions, values_struct& type, values_struct& id, values_struct& rule)
{
	if(!keyExistsInCurrScope(table, string(id.name)))
	{
		addSymbol(table, id.name, id);
		addFunction(functions, id.name, type.type);
		rule.type = type.type;
		id.type = type.type;
	}
	else
	{
		fprintf(stderr, "ERROR(line %i)->Function identifier already exists: %s\n", lineNumber, id.name);
		exit(14);
	}
}

void validateFunctionParams(function_table& functions, string functionName, values_struct& variable)
{
	if(vectorContains(functions[functionName].param_names, variable.name))
	{
		fprintf(stderr, "ERROR(line %i)->Function \'%s\' has a repeated parameter name: %s\n", lineNumber, functionName.c_str(), variable.name);
		exit(15);
	
	}else{
		functions[functionName].param_names.push_back(variable.name);
		functions[functionName].param_types.push_back(variable.type);
		
	}
}

bool vectorContains(vector<string> vec, string paramName)
{
	vector<string>::iterator it;
	for(it = vec.begin(); it < vec.end(); it++)
	{
		if(*it == paramName)
		{
			return true;
		}
		return false;
	}
}

void validateBreak(stack<string>& breakLabel)
{
	if(breakLabel.empty())
	{
		fprintf(stderr, "ERROR(line %i)->Invalid use of break\n", lineNumber);
		exit(16);
	}
}

void validateReturnVoid(function_table& functions, string functionName)
{
	type_name functionType = functions[functionName].type;
	if(functionType != void_type)
	{
		fprintf(stderr, "ERROR(line %i)->\'return;\' must be used only in void functions\n", lineNumber);
		exit(17);
	}
}

void validateReturnType(symbol_table& table,function_table& functions, string functionName, values_struct& variable)
{
	
	values_struct *valueBuscado = findKey(table, variable.name);
	
	if(valueBuscado != NULL ){
		if(valueBuscado->is_array == true){
			fprintf(stderr, "ERROR(line %i)->Can not return array (\'%s\')  \n", lineNumber,variable.name);
			exit(18);
		}

		type_name functionType = functions[functionName].type;

		if(functionType == void_type){
			fprintf(stderr, "ERROR(line %i)->Incorrect return for void function\n", lineNumber);
			exit(19);
		}
	
		if(variable.type != functionType){
			fprintf(stderr, "ERROR(line %i)->Return type different than function type\n", lineNumber);
			exit(20);
		}
	
	}
	
}

void validateFunctionCallName(function_table& functions, string functionName, string currFunctionName)
{
	if(!functionExists(functions, functionName))
	{
		fprintf(stderr, "ERROR(line %i)->Function call \'%s\' was not declared\n", lineNumber, functionName.c_str());
		exit(21);
	}
	
	// uncomment to disable recursion
	/*
	if(functionName == currFunctionName)
	{
		fprintf(stderr, "ERROR(line %i)->Function call \'%s\' is called recursively\n", lineNumber, functionName.c_str());
		exit(22);
	}
	*/
}

void validateFunctionCallParamsArray(symbol_table& table, values_struct& variable){

	for (symbol_table::iterator it = table.begin(); it < table.end(); it++) {
		map<string, values_struct> find_it = *it;
		for(map<string, values_struct>::iterator it2 = find_it.begin(); it2 != find_it.end(); it2++){
			values_struct& valueStruct = (*it2).second;								
			if((strcmp(variable.name, valueStruct.name) == 0) && ( ((valueStruct.is_array)? 0:1) == 0)){					
				fprintf(stderr, "ERROR(line %i)->Prohibit array as param in calling function \'%s\' \n", lineNumber, valueStruct.name);
				exit(23);
			}				
		}	
	}

}
void validateFunctionCallParams(symbol_table& table,function_table& functions, vector<type_name>& call_params, string functionName)
{

	int declNumParams = functions[functionName].param_names.size();
	int callNumParams = call_params.size();

	if(declNumParams != callNumParams){
		fprintf(stderr, "ERROR(line %i)->Number of arguments in function call \'%s\' is different than the number in function declaration\n", lineNumber, functionName.c_str());
		exit(24);
	}

	vector<type_name> decl_param_types = functions[functionName].param_types;	
	vector<string> decl_param_name = functions[functionName].param_names;	

	for(int i=0; i<declNumParams; i++){
		if(decl_param_types[i] != call_params[i]){
			fprintf(stderr, "ERROR(line %i)->Incongruent argument types between function call \'%s\' and its declaration\n", lineNumber, functionName.c_str());
			exit(25);
		}	
	}
}

void addCallParamType(vector<type_name>& param_types, type_name type)
{
	param_types.push_back(type);
}







	
